package utilities;


/**
 * @author Mathieu Rousseau
 * @email mathieu.rousseau3@mail.mcgill.ca
 */


import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.text.DateFormat;
import java.text.DecimalFormat;
import java.text.NumberFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Random;

/** ------------------------------------------------------------------------------------------------------------------
 * start of ExampleGenerator class
 * ------------------------------------------------------------------------------------------------------------------  **/
public class ExampleGenerator {
	
	
	/*  global variables  */
	private final static String inputPath = "ExampleGenerator/Input/";
	private final static String outputPath = "ExampleGenerator/Output/";	
	private static String filenameFragments = "simulated_fragments.txt";
	private static String filenameStructure = "simulated_structure.pdb";
	private static String filenameInputStructure = "no_name.pdb";
	private static String filenameInputFragments = "no_name.txt";
	private static String filenameIFs = "simulated_IFs.txt";
	private static boolean generateStructure = true;
	private static boolean generateFragments = true;
	private static int numberOfPoints = 0;
	private static double[][] structure;
	private static double[][] IFvalues;
	private static int[] fragments;
	private static DateFormat dateFormat = new SimpleDateFormat("yyyy/MM/dd\tHH:mm:ss");
	private static PDButility pdb = new PDButility();
	/*  end of global variables  */

	
	/*  start of generateFragments method  */
	private static void generateFragments(){
		fragments = new int[numberOfPoints];
		
		for(int i=0; i<numberOfPoints; i++){
			fragments[i] = (i * 100) + 1;
		}
		
		try{						
			BufferedWriter bw = new BufferedWriter(new FileWriter(outputPath + filenameFragments));
			
			//  write the fragments
			for(int i=0; i<numberOfPoints; i++){
				bw.write(fragments[i] + "");
				bw.flush();
				if(i != (numberOfPoints-1)){
					bw.newLine();
				}
			}
			bw.flush();
			bw.close();
		}catch (IOException e) {
			System.out.println("Error: could not output file: " + filenameFragments);
			e.printStackTrace();
		}//end of try-catch block
		System.out.println("Output file: " + outputPath + filenameFragments);
	}/*  end of generateFragments method  */
	
	
	/*  start of generateStructure method  */
	private static void generateStructure(){
		structure = new double[numberOfPoints][3];

		Random random = new Random();
		
		//  use counter1 for the x-coordinates 
		int counter1 = 1;
		//  use counte2 for the y-coordinates
		int counter2 = 2;
		//  use zStepSize for the z-coordinates
		double zStepSize = 5.0 / ((double)numberOfPoints);
		
		//  initialize the structure along a spiral with a bit of random noise
		for(int i=0; i<structure.length; i++){
			//  x-coordinate
			if(counter1 == 1 || counter1 == 2){
				structure[i][0] = 10 + random.nextDouble();
				counter1++;
			}
			else{
				structure[i][0] = 11 + random.nextDouble();
				counter1++;
			}
			//  reset the counter1
			if(counter1 == 5){
				counter1 = 1;
			}
			
			//  y-coordinate
			if(counter2 == 1 || counter2 == 2){
				structure[i][1] = 10 + random.nextDouble();
				counter2++;
			}
			else{
				structure[i][1] = 11 + random.nextDouble();
				counter2++;
			}
			//  reset the counter2
			if(counter2 == 5){
				counter2 = 1;
			}
			
			//  z-coordinate
			structure[i][2] = (i * zStepSize) + random.nextDouble();			
		}
		
		
		//  create the header and write the structure to file		
		String header = dateFormat.format(new Date()) + "\tnumberOfPoints=\t" + numberOfPoints;
		pdb.writeStructure(outputPath + filenameStructure, structure, header);
		
		System.out.println("Output file: " + outputPath + filenameStructure);
	}/*  end of generateStructure method  */
		
	
	/*  start of generateIFs method  */
	private static void generateIFs(boolean fullCoverage, boolean noise){
		
		IFvalues = new double[numberOfPoints][numberOfPoints];
		
		for(int i=0; i<numberOfPoints-1; i++){
			for(int j=i+1; j<numberOfPoints; j++){				
				double distanceBetweenPoints = 0.0;
				double squaredDistanceBetweenPoints = 0.0;
				double x1, y1, z1, x2, y2, z2;
				x1 = structure[i][0];
				y1 = structure[i][1];
				z1 = structure[i][2];
				x2 = structure[j][0];
				y2 = structure[j][1];
				z2 = structure[j][2];
				
				double term = x2 - x1;
				term *= term;
				squaredDistanceBetweenPoints += term;
				term = y2 - y1;
				term *= term;
				squaredDistanceBetweenPoints += term;
				term = z2 - z1;
				term *= term;
				squaredDistanceBetweenPoints += term;
				
				distanceBetweenPoints = Math.sqrt(squaredDistanceBetweenPoints);
				
				if(distanceBetweenPoints == 0){
					System.out.println("Error: the distance between point " + (i+1) + " and point " + (j+1) + " is zero (0). Cannot calculate IF value.\n" +
							"System.exit(0)");
					System.exit(0);
				}
				
//				IFvalues[i][j] = 1.0 / (double)distanceBetweenPoints;
				
				//  using an exponent of two (2.0) for distance <-> IF conversion
				//  i.e. IF = 1 / (distance^2)
				
				IFvalues[i][j] = 1.0 / (double)squaredDistanceBetweenPoints;
			}
		}
		
		try{						
			BufferedWriter bw = new BufferedWriter(new FileWriter(outputPath + filenameIFs));
			
			//  write the IFs as a list
			Random random = new Random();
			for(int i=0; i<numberOfPoints; i++){
				for(int j=i+1; j<numberOfPoints; j++){
					if(fullCoverage || (((i+j) % 2) == 1)){						
						//  simulate the noise associated with the IF value
						if(noise){
							double stdev = 0.2 * IFvalues[i][j];
							double gaussianNoise = random.nextGaussian() * stdev;
							double IFwithNoise = IFvalues[i][j] + gaussianNoise;
							while((IFwithNoise < 0) || ((stdev / IFwithNoise) > 0.75)){
								gaussianNoise = random.nextGaussian() * stdev;
								IFwithNoise = IFvalues[i][j] + gaussianNoise;
							}
							bw.write((i+1) + "\t" + (j+1) + "\t" + IFwithNoise + "\t" + stdev + "\t" + 3);
						}
						else{
							bw.write((i+1) + "\t" + (j+1) + "\t" + IFvalues[i][j] + "\t" + 0.01 + "\t" + 3);
						}
						bw.flush();
						if((i != (numberOfPoints-2)) && (i != (numberOfPoints-1))){
							bw.newLine();
						}
					}
				}
			}
			bw.flush();
			bw.close();
		}catch (IOException e) {
			System.out.println("Error: could not output file: " + filenameIFs);
			e.printStackTrace();
		}//end of try-catch block
		System.out.println("Output file: " + outputPath + filenameIFs);
	}/*  end of generateIFs method  */
	
	
	/*  start of getInputStructureFromFile method  */
	private static void getInputStructureFromFile(){
		//  determine the number of points from the input structure file
		numberOfPoints = pdb.getNumberOfPoints(inputPath + filenameInputStructure);
		System.out.println("Number of points determined from input structure file \"" + filenameInputStructure + "\" = " + numberOfPoints);
		
		//  get the input structure from file
		structure = pdb.getStructure(inputPath + filenameInputStructure);
		System.out.println("Input structure read from file \"" + filenameInputStructure + "\"");
	}/*  end of getInputStructureFromFile method  */
	
	
	/*  start of getInputFragmentsFromFile method  */
	private static void getInputFragmentsFromFile(){
		//  get all of the fragments from file
		fragments = new int[numberOfPoints];
		String line = null;
		int lineCounter = 0;
		try{
			BufferedReader br = new BufferedReader(new FileReader(inputPath + filenameInputFragments));			
			while((line = br.readLine()) != null){
				String values[] = line.split("\t");
				if(values.length == 1){
					fragments[lineCounter] = Integer.parseInt(values[0]);
				}
				else{
					System.out.println("Error: there is not one entry in each row in the fragments file\n"
							+ "System.exit(0)");
					System.exit(0);
				}
				lineCounter++;
			}
			br.close();
		}catch (IOException e) {
			e.printStackTrace();
			System.out.println("Error: cannot find feature file with filename \"" + filenameInputFragments + "\"\n" +
					"System.exit(0)");
			System.exit(0);
		}
		
		//  make sure the fragments file has the same number of points as the structure file
		if(lineCounter != numberOfPoints){
			System.out.println("Error: there are " + lineCounter + " fragments but " + numberOfPoints + " points." +
					"System.exit(0)");
			System.exit(0);
		}
		
		System.out.println("Input fragments read from file \"" + filenameInputFragments + "\"");
	}/*  end of getInputFragmentsFromFile method  */
	
	
	/*  start of main method  */
	public static void main(String[] args) {
		
		//  specify the structure and the fragments from which to generate simulated IF data
		filenameInputStructure = "best_structure_IFs_HB119.pdb";
		filenameInputFragments = "fragments_HB119.txt";
		
		
		if(!filenameInputStructure.equals("no_name.pdb")){
			//  get both the input structure and the number of points in the input structure
			getInputStructureFromFile();
			generateStructure = false;
		}
		else{
			//  need to set the number of points manually
			numberOfPoints = 36;
		}
		
		if(!filenameInputFragments.equals("no_name.txt")){
			getInputFragmentsFromFile();
			generateFragments = false;
		}
		
		if(generateFragments){
			generateFragments();
		}
		
		if(generateStructure){
			generateStructure();	
		}
		
		generateIFs(false, true);				//  parameters are: (full coverage, noise)
		
		System.out.println("Done!");
	}/*  end of main method  */

}/*  end of ExampleGenerator class  */